 /*******************************************************************************
  * Copyright (c) 2003, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.themes;

 import com.ibm.icu.text.MessageFormat;
 import java.util.ArrayList ;
 import java.util.Arrays ;
 import java.util.HashMap ;
 import java.util.HashSet ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;
 import java.util.ResourceBundle ;
 import java.util.Set ;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.preference.ColorSelector;
 import org.eclipse.jface.preference.PreferenceConverter;
 import org.eclipse.jface.preference.PreferencePage;
 import org.eclipse.jface.resource.JFaceResources;
 import org.eclipse.jface.resource.StringConverter;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.jface.viewers.AbstractTreeViewer;
 import org.eclipse.jface.viewers.DoubleClickEvent;
 import org.eclipse.jface.viewers.IDoubleClickListener;
 import org.eclipse.jface.viewers.IFontProvider;
 import org.eclipse.jface.viewers.ISelectionChangedListener;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.jface.viewers.LabelProvider;
 import org.eclipse.jface.viewers.LabelProviderChangedEvent;
 import org.eclipse.jface.viewers.SelectionChangedEvent;
 import org.eclipse.jface.viewers.StructuredSelection;
 import org.eclipse.jface.viewers.Viewer;
 import org.eclipse.jface.viewers.ViewerComparator;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.custom.StackLayout;
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.events.DisposeListener;
 import org.eclipse.swt.events.SelectionAdapter;
 import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.FontData;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.RGB;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.layout.GridData;
 import org.eclipse.swt.layout.GridLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.FontDialog;
 import org.eclipse.swt.widgets.Label;
 import org.eclipse.swt.widgets.Text;
 import org.eclipse.ui.IWorkbench;
 import org.eclipse.ui.IWorkbenchPreferencePage;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.dialogs.FilteredTree;
 import org.eclipse.ui.dialogs.PatternFilter;
 import org.eclipse.ui.internal.IWorkbenchGraphicConstants;
 import org.eclipse.ui.internal.IWorkbenchHelpContextIds;
 import org.eclipse.ui.internal.Workbench;
 import org.eclipse.ui.internal.WorkbenchMessages;
 import org.eclipse.ui.internal.WorkbenchPlugin;
 import org.eclipse.ui.internal.misc.StatusUtil;
 import org.eclipse.ui.internal.util.PrefUtil;
 import org.eclipse.ui.internal.util.Util;
 import org.eclipse.ui.themes.ITheme;
 import org.eclipse.ui.themes.IThemeManager;
 import org.eclipse.ui.themes.IThemePreview;

 /**
  * Preference page for management of system colors, gradients and fonts.
  *
  * @since 3.0
  */
 public final class ColorsAndFontsPreferencePage extends PreferencePage
         implements IWorkbenchPreferencePage {
     
     private static final String SELECTED_ELEMENT_PREF = "ColorsAndFontsPreferencePage.selectedElement"; //$NON-NLS-1$
 /**
      * The preference that stores the expanded state.
      */
     private static final String EXPANDED_ELEMENTS_PREF = "ColorsAndFontsPreferencePage.expandedCategories"; //$NON-NLS-1$
 /**
      * The token that seperates expanded elements in EXPANDED_ELEMENTS_PREF.
      */
     private static final String EXPANDED_ELEMENTS_TOKEN = "\t"; //$NON-NLS-1$

     /**
      * Marks category tokens in EXPANDED_ELEMENTS_PREF and SELECTED_ELEMENT_PREF.
      */
     private static final char MARKER_CATEGORY = 'T';
     
     /**
      * Marks color tokens in EXPANDED_ELEMENTS_PREF and SELECTED_ELEMENT_PREF.
      */
     private static final char MARKER_COLOR = 'C';
     
     /**
      * Marks font tokens in EXPANDED_ELEMENTS_PREF and SELECTED_ELEMENT_PREF.
      */
     private static final char MARKER_FONT = 'F';
             
     private class ThemeContentProvider implements ITreeContentProvider {

         private IThemeRegistry registry;

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
          */
         public Object [] getChildren(Object parentElement) {
             if (parentElement instanceof ThemeElementCategory) {
                 String categoryId = ((ThemeElementCategory) parentElement)
                         .getId();
                 Object [] defintions = (Object []) categoryMap.get(categoryId);
                 if (defintions == null) {
                     defintions = getCategoryChildren(categoryId);
                     categoryMap.put(categoryId, defintions);
                 }
                 return defintions;
             }

             ArrayList list = new ArrayList ();
             IHierarchalThemeElementDefinition def = (IHierarchalThemeElementDefinition) parentElement;
             String id = def.getId();
             IHierarchalThemeElementDefinition[] defs;
             if (def instanceof ColorDefinition) {
                 defs = registry.getColors();
             } else {
                 defs = registry.getFonts();
             }

             for (int i = 0; i < defs.length; i++) {
                 if (id.equals(defs[i].getDefaultsTo())
                         && ColorsAndFontsPreferencePage.equals(
                                 ((ICategorizedThemeElementDefinition) def)
                                         .getCategoryId(),
                                 ((ICategorizedThemeElementDefinition) defs[i])
                                         .getCategoryId())) {
                     list.add(defs[i]);
                 }
             }
             return list.toArray();
         }

         private Object [] getCategoryChildren(String categoryId) {
             ArrayList list = new ArrayList ();

             if (categoryId != null) {
                 ThemeElementCategory[] categories = registry.getCategories();
                 for (int i = 0; i < categories.length; i++) {
                     if (categoryId.equals(categories[i].getParentId())) {
                         Set bindings = themeRegistry
                                 .getPresentationsBindingsFor(categories[i]);
                         if (bindings == null
                                 || bindings.contains(workbench
                                         .getPresentationId())) {
                             list.add(categories[i]);
                         }
                     }
                 }
             }
             {
                 ColorDefinition[] colorDefinitions = themeRegistry
                         .getColorsFor(currentTheme.getId());
                 for (int i = 0; i < colorDefinitions.length; i++) {
                     if (!colorDefinitions[i].isEditable()) {
                         continue;
                     }
                     String catId = colorDefinitions[i].getCategoryId();
                     if ((catId == null && categoryId == null)
                             || (catId != null && categoryId != null && categoryId
                                     .equals(catId))) {
                         if (colorDefinitions[i].getDefaultsTo() != null
                                 && parentIsInSameCategory(colorDefinitions[i])) {
                             continue;
                         }
                         list.add(colorDefinitions[i]);
                     }
                 }
             }
             {
                 FontDefinition[] fontDefinitions = themeRegistry
                         .getFontsFor(currentTheme.getId());
                 for (int i = 0; i < fontDefinitions.length; i++) {
                     if (!fontDefinitions[i].isEditable()) {
                         continue;
                     }
                     String catId = fontDefinitions[i].getCategoryId();
                     if ((catId == null && categoryId == null)
                             || (catId != null && categoryId != null && categoryId
                                     .equals(catId))) {
                         if (fontDefinitions[i].getDefaultsTo() != null
                                 && parentIsInSameCategory(fontDefinitions[i])) {
                             continue;
                         }
                         list.add(fontDefinitions[i]);
                     }
                 }
             }
             return list.toArray(new Object [list.size()]);
         }

         /**
          * @param definition
          * @return
          */
         private boolean parentIsInSameCategory(ColorDefinition definition) {
             String defaultsTo = definition.getDefaultsTo();
             ColorDefinition[] defs = registry.getColors();
             for (int i = 0; i < defs.length; i++) {
                 if (defs[i].getId().equals(defaultsTo)
                         && ColorsAndFontsPreferencePage.equals(defs[i]
                                 .getCategoryId(), definition.getCategoryId())) {
                     return true;
                 }
             }
             return false;
         }

         /**
          * @param definition
          * @return
          */
         private boolean parentIsInSameCategory(FontDefinition definition) {
             String defaultsTo = definition.getDefaultsTo();
             FontDefinition[] defs = registry.getFonts();
             for (int i = 0; i < defs.length; i++) {
                 if (defs[i].getId().equals(defaultsTo)
                         && ColorsAndFontsPreferencePage.equals(defs[i]
                                 .getCategoryId(), definition.getCategoryId())) {
                     return true;
                 }
             }
             return false;
         }

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
          */
         public Object getParent(Object element) {
             return null;
         }

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
          */
         public boolean hasChildren(Object element) {
             if (element instanceof ThemeElementCategory) {
                 return true;
             }

             IHierarchalThemeElementDefinition def = (IHierarchalThemeElementDefinition) element;
             String id = def.getId();
             IHierarchalThemeElementDefinition[] defs;
             if (def instanceof ColorDefinition) {
                 defs = registry.getColors();
             } else {
                 defs = registry.getFonts();
             }

             for (int i = 0; i < defs.length; i++) {
                 if (id.equals(defs[i].getDefaultsTo())
                         && ColorsAndFontsPreferencePage.equals(
                                 ((ICategorizedThemeElementDefinition) def)
                                         .getCategoryId(),
                                 ((ICategorizedThemeElementDefinition) defs[i])
                                         .getCategoryId())) {
                     return true;
                 }
             }

             return false;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
          */
         public Object [] getElements(Object inputElement) {
             ArrayList list = new ArrayList ();
             Object [] uncatChildren = getCategoryChildren(null);
             list.addAll(Arrays.asList(uncatChildren));
             ThemeElementCategory[] categories = ((IThemeRegistry) inputElement)
                     .getCategories();
             for (int i = 0; i < categories.length; i++) {
                 if (categories[i].getParentId() == null) {
                     Set bindings = themeRegistry
                             .getPresentationsBindingsFor(categories[i]);
                     if (bindings == null
                             || bindings.contains(workbench.getPresentationId())) {
                         list.add(categories[i]);
                     }
                 }
             }
             return list.toArray(new Object [list.size()]);
         }

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.IContentProvider#dispose()
          */
         public void dispose() {
             categoryMap.clear();
         }

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
          */
         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
             categoryMap.clear();
             registry = (IThemeRegistry) newInput;
         }

     }

     private class PresentationLabelProvider extends LabelProvider implements
             IFontProvider {

         private HashMap fonts = new HashMap ();

         private HashMap images = new HashMap ();

         private int imageSize = -1;

         private int usableImageSize = -1;

         private IPropertyChangeListener listener = new IPropertyChangeListener() {
             public void propertyChange(PropertyChangeEvent event) {
                 fireLabelProviderChanged(new LabelProviderChangedEvent(
                         PresentationLabelProvider.this));
             }
         };

         private Image emptyImage;

         /**
          *
          */
         public PresentationLabelProvider() {
             hookListeners();
         }

         /**
          * Hook the listeners onto the various registries.
          */
         public void hookListeners() {
             colorRegistry.addListener(listener);
             fontRegistry.addListener(listener);
         }

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
          */
         public void dispose() {
             super.dispose();
             colorRegistry.removeListener(listener);
             fontRegistry.removeListener(listener);
             for (Iterator i = images.values().iterator(); i.hasNext();) {
                 ((Image) i.next()).dispose();
             }
             images.clear();

             if (emptyImage != null) {
                 emptyImage.dispose();
                 emptyImage = null;
             }

             //clear the fonts.
 clearFontCache();
         }

         /**
          * Clears and disposes all fonts.
          */
         public void clearFontCache() {
             for (Iterator i = fonts.values().iterator(); i.hasNext();) {
                 ((Font) i.next()).dispose();
             }
             fonts.clear();
         }
         
         /**
          * Clears and disposes all fonts and fires a label update.
          */
         public void clearFontCacheAndUpdate() {
             clearFontCache();
             fireLabelProviderChanged(new LabelProviderChangedEvent(
                     PresentationLabelProvider.this));
         }

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.IFontProvider#getFont(java.lang.Object)
          */
         public Font getFont(Object element) {
             Display display = tree.getDisplay();
             if (element instanceof FontDefinition) {
                 int parentHeight = tree.getViewer().getControl().getFont()
                         .getFontData()[0].getHeight();
                 Font baseFont = fontRegistry.get(((FontDefinition) element)
                         .getId());
                 Font font = (Font) fonts.get(baseFont);
                 if (font == null) {
                     FontData[] data = baseFont.getFontData();
                     for (int i = 0; i < data.length; i++) {
                         data[i].setHeight(parentHeight);
                     }
                     font = new Font(display, data);

                     fonts.put(baseFont, font);
                 }
                 return font;
             }

             return JFaceResources.getDialogFont();
         }

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.ILabelProvider#getImage(java.lang.Object)
          */
         public Image getImage(Object element) {
             if (element instanceof ColorDefinition) {
                 Color c = colorRegistry
                         .get(((ColorDefinition) element).getId());
                 Image image = (Image) images.get(c);
                 if (image == null) {
                     Display display = tree.getDisplay();
                     ensureImageSize(display);
                     //int size = presentationList.getControl().getFont().getFontData()[0].getHeight();
 image = new Image(display, imageSize, imageSize);

                     GC gc = new GC(image);
                     gc.setBackground(tree.getViewer().getControl()
                             .getBackground());
                     gc.setForeground(tree.getViewer().getControl()
                             .getBackground());
                     gc.drawRectangle(0, 0, imageSize - 1, imageSize - 1);

                     gc.setForeground(tree.getViewer().getControl()
                             .getForeground());
                     gc.setBackground(c);

                     int offset = (imageSize - usableImageSize) / 2;
                     gc.drawRectangle(offset, offset, usableImageSize - offset,
                             usableImageSize - offset);
                     gc.fillRectangle(offset + 1, offset + 1, usableImageSize
                             - offset - 1, usableImageSize - offset - 1);
                     gc.dispose();

                     images.put(c, image);
                 }
                 return image;

             } else if (element instanceof FontDefinition) {
                 return workbench.getSharedImages().getImage(
                         IWorkbenchGraphicConstants.IMG_OBJ_FONT);
             } else {
                 return workbench.getSharedImages().getImage(
                         IWorkbenchGraphicConstants.IMG_OBJ_THEME_CATEGORY);
             }
         }

         /**
          * @param display
          * @return
          */
         private void ensureImageSize(Display display) {
             if (imageSize == -1) {
                 imageSize = tree.getViewer().getTree().getItemHeight();
                 usableImageSize = Math.max(1, imageSize - 4);
             }
         }

         /* (non-Javadoc)
          * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)
          */
         public String getText(Object element) {
             if (element instanceof IHierarchalThemeElementDefinition) {
                 IHierarchalThemeElementDefinition themeElement = (IHierarchalThemeElementDefinition) element;
                 if (themeElement
                         .getDefaultsTo() != null) {
                     String myCategory = ((ICategorizedThemeElementDefinition) themeElement)
                             .getCategoryId();
                     ICategorizedThemeElementDefinition def;
                     if (element instanceof ColorDefinition) {
                         def = themeRegistry
                                 .findColor(themeElement
                                         .getDefaultsTo());
                     } else {
                         def = themeRegistry
                                 .findFont(themeElement
                                         .getDefaultsTo());
                     }

                     if (!ColorsAndFontsPreferencePage.equals(def
                             .getCategoryId(), myCategory)) {
                             if (isDefault(themeElement)) {
                             return MessageFormat
                                     .format(
                                             RESOURCE_BUNDLE
                                                     .getString("defaultFormat_default"), new Object [] { themeElement.getName(), def.getName() }); //$NON-NLS-1$
 }
                         
                             return MessageFormat
                             .format(
                                     RESOURCE_BUNDLE
                                             .getString("defaultFormat_override"), new Object [] { themeElement.getName(), def.getName() }); //$NON-NLS-1$
 }
                 }
             }
             return ((IThemeElementDefinition) element).getName();
         }

         /**
          * Return whether the element is set to default.
          *
          * @param def the definition
          * @return whether the element is set to default
          * @since 3.2
          */
         private boolean isDefault(IThemeElementDefinition def) {
             if (def instanceof FontDefinition) {
                 return ColorsAndFontsPreferencePage.this.isDefault((FontDefinition)def);
             } else if (def instanceof ColorDefinition) {
                 return ColorsAndFontsPreferencePage.this.isDefault((ColorDefinition)def);
             }
             return false;
         }
     }

     /**
      * The translation bundle in which to look up internationalized text.
      */
     private final static ResourceBundle RESOURCE_BUNDLE = ResourceBundle
             .getBundle(ColorsAndFontsPreferencePage.class.getName());

     /**
      * Map to precalculate category color lists.
      */
     private Map categoryMap = new HashMap (7);

     private Font appliedDialogFont;

     /**
      * The composite containing all color-specific controls.
      */
     private Composite colorControls;

     /**
      * Map of defintion id->RGB objects that map to changes expressed in this
      * UI session. These changes should be made in preferences and the
      * registry.
      */
     private Map colorPreferencesToSet = new HashMap (7);

     private CascadingColorRegistry colorRegistry;

     private Button colorResetButton;

     private ColorSelector colorSelector;

     /**
      * Map of defintion id->RGB objects that map to changes expressed in this
      * UI session. These changes should be made in the registry.
      */
     private Map colorValuesToSet = new HashMap (7);

     /**
      * The composite that contains the font or color controls (or none).
      */
     private Composite controlArea;

     /**
      * The layout for the controlArea.
      */
     private StackLayout controlAreaLayout;

     /**
      * The composite to use when no preview is available.
      */
     private Composite defaultPreviewControl;

     private Text descriptionText;

     private List dialogFontWidgets = new ArrayList ();

     private Button fontChangeButton;

     /**
      * The composite containing all font-specific controls.
      */
     private Composite fontControls;

     private Map fontPreferencesToSet = new HashMap (7);

     private CascadingFontRegistry fontRegistry;

     private Button fontResetButton;

     private Button fontSystemButton;

     /**
      * Map of defintion id->FontData[] objects that map to changes expressed in
      * this UI session. These changes should be made in preferences and the
      * registry.
      */
     private Map fontValuesToSet = new HashMap (7);

     /**
      * The list of fonts and colors.
      */
     //private TreeViewer presentationList;
 /**
      * The composite that is parent to all previews.
      */
     private Composite previewComposite;

     /**
      * A mapping from PresentationCategory->Composite for the created previews.
      */
     private Map previewMap = new HashMap (7);

     /**
      * Set containing all IPresentationPreviews created.
      */
     private Set previewSet = new HashSet (7);

     /**
      * The layout for the previewComposite.
      */
     private StackLayout stackLayout;

     private final IThemeRegistry themeRegistry;

     private ITheme currentTheme;

     private PresentationLabelProvider labelProvider;

     private CascadingTheme cascadingTheme;

     private IPropertyChangeListener themeChangeListener;

     private Workbench workbench;

     private FilteredTree tree;

     /**
      * Create a new instance of the receiver.
      */
     public ColorsAndFontsPreferencePage() {
         themeRegistry = WorkbenchPlugin.getDefault().getThemeRegistry();
         //no-op
 }

     /**
      * @param string
      * @param string2
      * @return
      */
     private static boolean equals(String string, String string2) {
         if ((string == null && string2 == null)) {
             return true;
         }
         if (string == null || string2 == null) {
             return false;
         }
         if (string.equals(string2)) {
             return true;
         }

         return false;
     }

     /**
      * Create a button for the preference page.
      * @param parent
      * @param label
      */
     private Button createButton(Composite parent, String label) {
         Button button = new Button(parent, SWT.PUSH | SWT.CENTER);
         button.setText(label);
         myApplyDialogFont(button);
         setButtonLayoutData(button);
         button.setEnabled(false);
         return button;
     }

     /**
      * Create the color selection control.
      */
     private void createColorControl() {
         Composite composite = new Composite(colorControls, SWT.NONE);
         GridLayout layout = new GridLayout(2, false);
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         composite.setLayout(layout);

         colorSelector = new ColorSelector(composite);
         colorSelector.getButton().setLayoutData(new GridData());
         myApplyDialogFont(colorSelector.getButton());
         colorSelector.setEnabled(false);

         colorResetButton = createButton(composite, RESOURCE_BUNDLE
                 .getString("reset")); //$NON-NLS-1$
 }

     /* (non-Javadoc)
      * @see org.eclipse.jface.preference.PreferencePage#createContents(org.eclipse.swt.widgets.Composite)
      */
     protected Control createContents(Composite parent) {
         
         PlatformUI.getWorkbench().getHelpSystem().setHelp(parent,
                 IWorkbenchHelpContextIds.FONTS_PREFERENCE_PAGE);
         
         parent.addDisposeListener(new DisposeListener() {
             public void widgetDisposed(DisposeEvent e) {
                 if (appliedDialogFont != null) {
                     appliedDialogFont.dispose();
                 }
             }
         });
         Composite mainColumn = new Composite(parent, SWT.NONE);
         GridLayout layout = new GridLayout();
         layout.marginWidth = 0;
         layout.marginHeight = 0;
         mainColumn.setFont(parent.getFont());
         mainColumn.setLayout(layout);

         GridData data = new GridData(GridData.BEGINNING);
         Label label = new Label(mainColumn, SWT.LEFT);
         label.setText(RESOURCE_BUNDLE.getString("colorsAndFonts")); //$NON-NLS-1$
 myApplyDialogFont(label);
         label.setLayoutData(data);

         Composite controlRow = new Composite(mainColumn, SWT.NONE);
         layout = new GridLayout();
         layout.numColumns = 2;
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         controlRow.setLayout(layout);
         data = new GridData(GridData.FILL_HORIZONTAL);
         controlRow.setLayoutData(data);

         createTree(controlRow);
         Composite controlColumn = new Composite(controlRow, SWT.NONE);
         data = new GridData(GridData.FILL_VERTICAL);
         controlColumn.setLayoutData(data);
         layout = new GridLayout();
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         controlColumn.setLayout(layout);

         controlArea = new Composite(controlColumn, SWT.NONE);
         controlAreaLayout = new StackLayout();
         controlArea.setLayout(controlAreaLayout);

         colorControls = new Composite(controlArea, SWT.NONE);
         colorControls.setLayout(new FillLayout());
         createColorControl();

         fontControls = new Composite(controlArea, SWT.NONE);
         fontControls.setLayout(new FillLayout());
         createFontControl();

         createDescriptionControl(mainColumn);

         createPreviewControl(mainColumn);

         hookListeners();

         return mainColumn;
     }

     /**
      * Create the text box that will contain the current color/font description
      * text (if any).
      *
      * @param parent the parent <code>Composite</code>.
      */
     private void createDescriptionControl(Composite parent) {
         Composite composite = new Composite(parent, SWT.NONE);
         GridLayout layout = new GridLayout();
         layout.marginWidth = 0;
         layout.marginHeight = 0;
         composite.setLayout(layout);
         GridData data = new GridData(GridData.FILL_BOTH);
         data.heightHint = convertHeightInCharsToPixels(5);
         composite.setLayoutData(data);

         Label label = new Label(composite, SWT.LEFT);
         label.setText(RESOURCE_BUNDLE.getString("description")); //$NON-NLS-1$
 myApplyDialogFont(label);

         descriptionText = new Text(composite, SWT.H_SCROLL | SWT.V_SCROLL
                 | SWT.READ_ONLY | SWT.BORDER | SWT.WRAP);
         data = new GridData(GridData.FILL_BOTH);
         descriptionText.setLayoutData(data);
         myApplyDialogFont(descriptionText);
     }

     private void createFontControl() {
         Composite composite = new Composite(fontControls, SWT.NONE);
         GridLayout layout = new GridLayout(1, false);
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         composite.setLayout(layout);

         fontSystemButton = createButton(composite, WorkbenchMessages.FontsPreference_useSystemFont);

         fontChangeButton = createButton(composite, JFaceResources
                 .getString("openChange")); //$NON-NLS-1$

         fontResetButton = createButton(composite, RESOURCE_BUNDLE
                 .getString("reset")); //$NON-NLS-1$
 }

     /**
      * Create the <code>ListViewer</code> that will contain all color
      * definitions as defined in the extension point.
      *
      * @param parent the parent <code>Composite</code>.
      */
     private void createTree(Composite parent) {
         labelProvider = new PresentationLabelProvider();
         // create a new tree with a custom pattern matcher that will allow
 // non-category elements to be returned in the event that their children
 // do not
 tree = new FilteredTree(parent, SWT.SINGLE | SWT.H_SCROLL
                 | SWT.V_SCROLL | SWT.BORDER, new PatternFilter() {
             
             /* (non-Javadoc)
              * @see org.eclipse.ui.dialogs.PatternFilter#isParentMatch(org.eclipse.jface.viewers.Viewer, java.lang.Object)
              */
             protected boolean isParentMatch(Viewer viewer, Object element) {
                 Object [] children = ((ITreeContentProvider) ((AbstractTreeViewer) viewer)
                         .getContentProvider()).getChildren(element);
                 if (children.length > 0
                         && element instanceof ThemeElementCategory) {
                     return filter(viewer, element, children).length > 0;
                 }
                 return false;
             }
         });

         GridData data = new GridData(GridData.FILL_HORIZONTAL
                 | GridData.VERTICAL_ALIGN_FILL);
         data.heightHint = Math.max(175, convertHeightInCharsToPixels(10));
         tree.setLayoutData(data);
         myApplyDialogFont(tree.getViewer().getControl());
         Text filterText = tree.getFilterControl();
         if (filterText != null) {
             myApplyDialogFont(filterText);
         }

         tree.getViewer().setLabelProvider(labelProvider);
         tree.getViewer().setContentProvider(new ThemeContentProvider());
         tree.getViewer().setComparator(new ViewerComparator() {
             /* (non-Javadoc)
              * @see org.eclipse.jface.viewers.ViewerComparator#category(java.lang.Object)
              */
             public int category(Object element) {
                 if (element instanceof ThemeElementCategory) {
                     return 0;
                 }
                 return 1;
             }
         });
         tree.getViewer().setInput(
                 WorkbenchPlugin.getDefault().getThemeRegistry());
         tree.getViewer().addDoubleClickListener(new IDoubleClickListener() {
             /*
              * (non-Javadoc)
              *
              * @see org.eclipse.jface.viewers.IDoubleClickListener#doubleClick(org.eclipse.jface.viewers.DoubleClickEvent)
              */
             public void doubleClick(DoubleClickEvent event) {
                 IStructuredSelection s = (IStructuredSelection) event
                         .getSelection();
                 Object element = s.getFirstElement();
                 if (tree.getViewer().isExpandable(element)) {
                     tree.getViewer().setExpandedState(element,
                             !tree.getViewer().getExpandedState(element));
                 }
                 
                 if (element instanceof FontDefinition) {
                         editFont(tree.getDisplay());
                 }
                 else if (element instanceof ColorDefinition) {
                         colorSelector.open();
                 }
             }
         });
         
         restoreTreeExpansion();
         restoreTreeSelection();
     }

     /**
      * @param mainColumn
      */
     private void createPreviewControl(Composite mainColumn) {
         Composite composite = new Composite(mainColumn, SWT.NONE);
         GridData data = new GridData(GridData.FILL_BOTH);
         data.heightHint = 175;
         composite.setLayoutData(data);
         GridLayout layout = new GridLayout(1, true);
         layout.marginHeight = 0;
         layout.marginWidth = 0;
         composite.setLayout(layout);

         Label label = new Label(composite, SWT.LEFT);
         label.setText(RESOURCE_BUNDLE.getString("preview")); //$NON-NLS-1$
 myApplyDialogFont(label);
         previewComposite = new Composite(composite, SWT.NONE);
         data = new GridData(GridData.FILL_BOTH);
         previewComposite.setLayoutData(data);
         stackLayout = new StackLayout();
         previewComposite.setLayout(stackLayout);
     }

     /* (non-Javadoc)
      * @see org.eclipse.jface.dialogs.IDialogPage#dispose()
      */
     public void dispose() {
         super.dispose();

         workbench.getThemeManager().removePropertyChangeListener(
                 themeChangeListener);

         clearPreviews();

         colorRegistry.dispose();
         fontRegistry.dispose();

     }

     /**
      * Clear all previews.
      */
     private void clearPreviews() {
         if (cascadingTheme != null) {
             cascadingTheme.dispose();
         }

         for (Iterator i = previewSet.iterator(); i.hasNext();) {
             IThemePreview preview = (IThemePreview) i.next();
             try {
                 preview.dispose();
             } catch (RuntimeException e) {
                 WorkbenchPlugin
                         .log(
                                 RESOURCE_BUNDLE
                                         .getString("errorDisposePreviewLog"), StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e)); //$NON-NLS-1$
 }
         }

         previewSet.clear();
     }

     /**
      * Get the ancestor of the given color, if any.
      *
      * @param definition the descendant <code>ColorDefinition</code>.
      * @return the ancestror <code>ColorDefinition</code>, or <code>null</code>
      * if none.
      */
     private ColorDefinition getColorAncestor(ColorDefinition definition) {
         String defaultsTo = definition.getDefaultsTo();
         if (defaultsTo == null) {
             return null;
         }

         return themeRegistry.findColor(defaultsTo);
     }

     /**
      * Get the RGB value of the given colors ancestor, if any.
      *
      * @param definition the descendant <code>ColorDefinition</code>.
      * @return the ancestror <code>RGB</code>, or <code>null</code> if none.
      */
     private RGB getColorAncestorValue(ColorDefinition definition) {
         ColorDefinition ancestor = getColorAncestor(definition);
         if (ancestor == null) {
             return null;
        }

        return getColorValue(ancestor);
    }

    /**
     * Get the RGB value for the specified definition. Cascades through
     * preferenceToSet, valuesToSet and finally the registry.
     *
     * @param definition the <code>ColorDefinition</code>.
     * @return the <code>RGB</code> value.
     */
    private RGB getColorValue(ColorDefinition definition) {
        String id = definition.getId();
        RGB updatedRGB = (RGB) colorPreferencesToSet.get(id);
        if (updatedRGB == null) {
            updatedRGB = (RGB) colorValuesToSet.get(id);
            if (updatedRGB == null) {
                updatedRGB = currentTheme.getColorRegistry().getRGB(id);
            }
        }
        return updatedRGB;
    }

    /**
     * @return Return the default "No preview available." preview.
     */
    private Composite getDefaultPreviewControl() {
        if (defaultPreviewControl == null) {
            defaultPreviewControl = new Composite(previewComposite, SWT.NONE);
            defaultPreviewControl.setLayout(new FillLayout());
            Label l = new Label(defaultPreviewControl, SWT.LEFT);
            l.setText(RESOURCE_BUNDLE.getString("noPreviewAvailable")); //$NON-NLS-1$
 myApplyDialogFont(l);
        }
        return defaultPreviewControl;
    }

    /**
     * Get colors that descend from the provided color.
     *
     * @param definition the ancestor <code>ColorDefinition</code>.
     * @return the ColorDefinitions that have the provided definition as their
     * defaultsTo attribute.
     */
    private ColorDefinition[] getDescendantColors(ColorDefinition definition) {
        List list = new ArrayList (5);
        String id = definition.getId();

        ColorDefinition[] colors = themeRegistry.getColors();
        ColorDefinition[] sorted = new ColorDefinition[colors.length];
        System.arraycopy(colors, 0, sorted, 0, sorted.length);

        Arrays.sort(sorted, new IThemeRegistry.HierarchyComparator(colors));

        for (int i = 0; i < sorted.length; i++) {
            if (id.equals(sorted[i].getDefaultsTo())) {
                list.add(sorted[i]);
            }
        }

        return (ColorDefinition[]) list
                .toArray(new ColorDefinition[list.size()]);
    }

    /**
     * @param definition
     * @return
     */
    private FontDefinition[] getDescendantFonts(FontDefinition definition) {
        List list = new ArrayList (5);
        String id = definition.getId();

        FontDefinition[] fonts = themeRegistry.getFonts();
        FontDefinition[] sorted = new FontDefinition[fonts.length];
        System.arraycopy(fonts, 0, sorted, 0, sorted.length);

        Arrays.sort(sorted, new IThemeRegistry.HierarchyComparator(fonts));

        for (int i = 0; i < sorted.length; i++) {
            if (id.equals(sorted[i].getDefaultsTo())) {
                list.add(sorted[i]);
            }
        }

        return (FontDefinition[]) list.toArray(new FontDefinition[list.size()]);
    }

    /**
     * @param definition
     * @return
     */
    private FontDefinition getFontAncestor(FontDefinition definition) {
        String defaultsTo = definition.getDefaultsTo();
        if (defaultsTo == null) {
            return null;
        }

        return themeRegistry.findFont(defaultsTo);
    }

    /**
     * @param definition
     * @return
     */
    private FontData[] getFontAncestorValue(FontDefinition definition) {
        FontDefinition ancestor = getFontAncestor(definition);
        if (ancestor == null) {
            return PreferenceConverter.getDefaultFontDataArray(
                    getPreferenceStore(), ThemeElementHelper
                            .createPreferenceKey(currentTheme, definition
                                    .getId()));
        }

        return getFontValue(ancestor);
    }

    /**
     * @param definition
     * @return
     */
    protected FontData[] getFontValue(FontDefinition definition) {
        String id = definition.getId();
        FontData[] updatedFD = (FontData[]) fontPreferencesToSet.get(id);
        if (updatedFD == null) {
            updatedFD = (FontData[]) fontValuesToSet.get(id);
            if (updatedFD == null) {
                updatedFD = currentTheme.getFontRegistry().getFontData(id);
            }
        }
        return updatedFD;
    }

    /**
     * @return
     */
    protected ColorDefinition getSelectedColorDefinition() {
        Object o = ((IStructuredSelection) tree.getViewer().getSelection())
                .getFirstElement();
        if (o instanceof ColorDefinition) {
            return (ColorDefinition) o;
        }
        return null;
    }

    /**
     * @return
     */
    protected FontDefinition getSelectedFontDefinition() {
        Object o = ((IStructuredSelection) tree.getViewer().getSelection())
                .getFirstElement();
        if (o instanceof FontDefinition) {
            return (FontDefinition) o;
        }
        return null;
    }

    /**
     * Hook all control listeners.
     */
    private void hookListeners() {
        colorSelector.addListener(new IPropertyChangeListener() {

            /* (non-Javadoc)
             * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
             */
            public void propertyChange(PropertyChangeEvent event) {
                ColorDefinition definition = getSelectedColorDefinition();

                RGB newRGB = (RGB) event.getNewValue();
                if (definition != null && newRGB != null
                        && !newRGB.equals(event.getOldValue())) {
                    setColorPreferenceValue(definition, newRGB);
                    setRegistryValue(definition, newRGB);
                }

                updateColorControls(definition);
            }
        });

        tree.getViewer().addSelectionChangedListener(
                new ISelectionChangedListener() {

                    /* (non-Javadoc)
                     * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
                     */
                    public void selectionChanged(SelectionChangedEvent event) {
                        if (event.getSelection().isEmpty()) {
                            swapNoControls();
                            updateColorControls(null);
                            updateCategorySelection(null);
                        } else {
                            Object element = ((IStructuredSelection) event
                                    .getSelection()).getFirstElement();
                            if (element instanceof ThemeElementCategory) {
                                swapNoControls();
                                String description = ((ThemeElementCategory) element)
                                        .getDescription();
                                descriptionText
                                        .setText(description == null ? "" : description); //$NON-NLS-1$
 updateCategorySelection((ThemeElementCategory) element);
                            } else if (element instanceof ColorDefinition) {
                                updateColorControls((ColorDefinition) element);
                                swapColorControls();
                                updateCategorySelection(WorkbenchPlugin
                                        .getDefault().getThemeRegistry()
                                        .findCategory(
                                                ((ColorDefinition) element)
                                                        .getCategoryId()));
                            } else if (element instanceof FontDefinition) {
                                updateFontControls((FontDefinition) element);
                                swapFontControls();
                                updateCategorySelection(WorkbenchPlugin
                                        .getDefault().getThemeRegistry()
                                        .findCategory(
                                                ((FontDefinition) element)
                                                        .getCategoryId()));
                            }
                        }
                    }
                });

        colorResetButton.addSelectionListener(new SelectionAdapter() {

            /* (non-Javadoc)
             * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
             */
            public void widgetSelected(SelectionEvent e) {
                ColorDefinition definition = getSelectedColorDefinition();
                if (resetColor(definition)) {
                    updateColorControls(definition);
                }
            }
        });

        fontResetButton.addSelectionListener(new SelectionAdapter() {

            /* (non-Javadoc)
             * @see org.eclipse.swt.events.SelectionListener#widgetSelected(org.eclipse.swt.events.SelectionEvent)
             */
            public void widgetSelected(SelectionEvent e) {
                FontDefinition definition = getSelectedFontDefinition();
                if (resetFont(definition)) {
                    updateFontControls(definition);
                }
            }
        });

        fontChangeButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                Display display = event.display;
                editFont(display);
            }
        });

        fontSystemButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                FontDefinition definition = getSelectedFontDefinition();
                if (definition != null) {
                    FontData[] defaultFontData = JFaceResources
                            .getDefaultFont().getFontData();
                    setFontPreferenceValue(definition, defaultFontData);
                    setRegistryValue(definition, defaultFontData);

                    updateFontControls(definition);
                }
            }
        });
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
     */
    public void init(IWorkbench aWorkbench) {
        this.workbench = (Workbench) aWorkbench;
        setPreferenceStore(PrefUtil.getInternalPreferenceStore());

        final IThemeManager themeManager = aWorkbench.getThemeManager();

        themeChangeListener = new IPropertyChangeListener() {

            /* (non-Javadoc)
             * @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
             */
            public void propertyChange(PropertyChangeEvent event) {
                if (event.getProperty().equals(
                        IThemeManager.CHANGE_CURRENT_THEME)) {
                    updateThemeInfo(themeManager);
                    refreshCategory();
                    tree.getViewer().refresh(); // refresh all the labels in the tree
 }
            }
        };
        themeManager.addPropertyChangeListener(themeChangeListener);

        updateThemeInfo(themeManager);
    }

    private void updateThemeInfo(IThemeManager manager) {
        clearPreviews();
        categoryMap.clear();

        if (labelProvider != null) {
            labelProvider.dispose(); // nuke the old cache
 }

        currentTheme = manager.getCurrentTheme();

        if (colorRegistry != null) {
            colorRegistry.dispose();
        }
        if (fontRegistry != null) {
            fontRegistry.dispose();
        }

        currentTheme = manager.getCurrentTheme();

        colorRegistry = new CascadingColorRegistry(currentTheme
                .getColorRegistry());
        fontRegistry = new CascadingFontRegistry(currentTheme.getFontRegistry());

        fontPreferencesToSet.clear();
        fontValuesToSet.clear();

        colorPreferencesToSet.clear();
        colorValuesToSet.clear();

        if (labelProvider != null) {
            labelProvider.hookListeners(); // rehook the listeners
 }
    }

    /**
     * Answers whether the definition is currently set to the default value.
     *
     * @param definition the <code>ColorDefinition</code> to check.
     * @return Return whether the definition is currently mapped to the default
     * value, either in the preference store or in the local change record
     * of this preference page.
     */
    private boolean isDefault(ColorDefinition definition) {
        String id = definition.getId();

        if (colorPreferencesToSet.containsKey(id)) {
            if (definition.getValue() != null) { // value-based color
 if (colorPreferencesToSet.get(id).equals(definition.getValue())) {
                    return true;
                }
            } else {
                if (colorPreferencesToSet.get(id).equals(
                        getColorAncestorValue(definition))) {
                    return true;
                }
            }
        } else {
            if (definition.getValue() != null) { // value-based color
 if (getPreferenceStore().isDefault(
                        ThemeElementHelper
                                .createPreferenceKey(currentTheme, id))) {
                    return true;
                }
            } else {
                // a descendant is default if it's the same value as its ancestor
 if (getColorValue(definition).equals(
                        getColorAncestorValue(definition))) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param definition
     * @return
     */
    private boolean isDefault(FontDefinition definition) {
        String id = definition.getId();

        if (fontPreferencesToSet.containsKey(id)) {
            if (definition.getValue() != null) { // value-based font
 if (Arrays.equals((FontData[]) fontPreferencesToSet.get(id),
                        definition.getValue())) {
                    return true;
                }
            } else {
                FontData[] ancestor = getFontAncestorValue(definition);
                if (Arrays.equals((FontData[]) fontPreferencesToSet.get(id),
                        ancestor)) {
                    return true;
                }
            }
        } else {
            if (definition.getValue() != null) { // value-based font
 if (getPreferenceStore().isDefault(
                        ThemeElementHelper
                                .createPreferenceKey(currentTheme, id))) {
                    return true;
                }
            } else {
                FontData[] ancestor = getFontAncestorValue(definition);
                if (ancestor == null) {
                    return true;
                }

                // a descendant is default if it's the same value as its ancestor
 if (Arrays.equals(getFontValue(definition), ancestor)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Apply the dialog font to the control and store
     * it for later so that it can be used for a later
     * update.
     * @param control
     */
    private void myApplyDialogFont(Control control) {
        control.setFont(JFaceResources.getDialogFont());
        dialogFontWidgets.add(control);
    }

    /**
     * @see org.eclipse.jface.preference.PreferencePage#performApply()
     */
    protected void performApply() {
        super.performApply();

        //Apply the default font to the dialog.
 Font oldFont = appliedDialogFont;

        FontDefinition fontDefinition = themeRegistry
                .findFont(JFaceResources.DIALOG_FONT);
        if (fontDefinition == null) {
            return;
        }

        FontData[] newData = getFontValue(fontDefinition);

        appliedDialogFont = new Font(getControl().getDisplay(), newData);

        updateForDialogFontChange(appliedDialogFont);
        getApplyButton().setFont(appliedDialogFont);
        getDefaultsButton().setFont(appliedDialogFont);

        if (oldFont != null) {
            oldFont.dispose();
        }
    }

    /**
     *
     */
    private void performColorDefaults() {
        ColorDefinition[] definitions = themeRegistry.getColors();

        // apply defaults in depth-order.
 ColorDefinition[] definitionsCopy = new ColorDefinition[definitions.length];
        System
                .arraycopy(definitions, 0, definitionsCopy, 0,
                        definitions.length);

        Arrays.sort(definitionsCopy, new IThemeRegistry.HierarchyComparator(
                definitions));

        for (int i = 0; i < definitionsCopy.length; i++) {
            resetColor(definitionsCopy[i]);
        }

        updateColorControls(getSelectedColorDefinition());
    }

    /**
     * @return
     */
    private boolean performColorOk() {
        for (Iterator i = colorPreferencesToSet.keySet().iterator(); i
                .hasNext();) {
            String id = (String ) i.next();
            String key = ThemeElementHelper.createPreferenceKey(currentTheme,
                    id);
            RGB rgb = (RGB) colorPreferencesToSet.get(id);
            String rgbString = StringConverter.asString(rgb);
            String storeString = getPreferenceStore().getString(key);

            if (!rgbString.equals(storeString)) {
                getPreferenceStore().setValue(key, rgbString);
            }
        }

        colorValuesToSet.clear();
        colorPreferencesToSet.clear();
        
        return true;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.preference.PreferencePage#performDefaults()
     */
    protected void performDefaults() {
        performColorDefaults();
        performFontDefaults();
    }

    /**
     *
     */
    private void performFontDefaults() {
        FontDefinition[] definitions = themeRegistry.getFonts();

        // apply defaults in depth-order.
 FontDefinition[] definitionsCopy = new FontDefinition[definitions.length];
        System
                .arraycopy(definitions, 0, definitionsCopy, 0,
                        definitions.length);

        Arrays.sort(definitionsCopy, new IThemeRegistry.HierarchyComparator(
                definitions));

        for (int i = 0; i < definitionsCopy.length; i++) {
            resetFont(definitionsCopy[i]);
        }

        updateFontControls(getSelectedFontDefinition());
    }

    /**
     * @return
     */
    private boolean performFontOk() {
        for (Iterator i = fontPreferencesToSet.keySet().iterator(); i.hasNext();) {
            String id = (String ) i.next();
            String key = ThemeElementHelper.createPreferenceKey(currentTheme,
                    id);
            FontData[] fd = (FontData[]) fontPreferencesToSet.get(id);

            String fdString = PreferenceConverter.getStoredRepresentation(fd);
            String storeString = getPreferenceStore().getString(key);

            if (!fdString.equals(storeString)) {
                getPreferenceStore().setValue(key, fdString);
            }
        }

        fontValuesToSet.clear();
        fontPreferencesToSet.clear();
        return true;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.preference.IPreferencePage#performOk()
     */
    public boolean performOk() {
        saveTreeExpansion();
        saveTreeSelection();
        boolean result = performColorOk() && performFontOk();
        if(result) {
            PrefUtil.savePrefs();
        }
        return result;
    }

    /**
     * Refreshes the category.
     */
    private void refreshCategory() {
        updateColorControls(null);
        updateFontControls(null);
    }

    /**
     * Resets the supplied definition to its default value.
     *
     * @param definition the <code>ColorDefinition</code> to reset.
     * @return whether any change was made.
     */
    private boolean resetColor(ColorDefinition definition) {
        if (!isDefault(definition)) {

            RGB newRGB;
            if (definition.getValue() != null) {
                newRGB = definition.getValue();
            } else {
                newRGB = getColorAncestorValue(definition);
            }

            if (newRGB != null) {
                setColorPreferenceValue(definition, newRGB);
                setRegistryValue(definition, newRGB);
                return true;
            }
        }
        return false;
    }

    /**
     * @param definition
     * @return
     */
    protected boolean resetFont(FontDefinition definition) {
        if (!isDefault(definition)) {

            FontData[] newFD;
            if (definition.getDefaultsTo() != null) {
                newFD = getFontAncestorValue(definition);
            } else {
                newFD = PreferenceConverter.getDefaultFontDataArray(
                        getPreferenceStore(), ThemeElementHelper
                                .createPreferenceKey(currentTheme, definition
                                        .getId()));
            }

            if (newFD != null) {
                setFontPreferenceValue(definition, newFD);
                setRegistryValue(definition, newFD);
                return true;
            }
        }
        return false;
    }

    /**
     * Set the value (in preferences) for the given color.
     *
     * @param definition the <code>ColorDefinition</code> to set.
     * @param newRGB the new <code>RGB</code> value for the definitions
     * identifier.
     */
    protected void setColorPreferenceValue(ColorDefinition definition,
            RGB newRGB) {
        setDescendantRegistryValues(definition, newRGB);
        colorPreferencesToSet.put(definition.getId(), newRGB);
    }

    /**
     * Set the value (in registry) for the given colors children.
     *
     * @param definition the <code>ColorDefinition</code> whos children should
     * be set.
     * @param newRGB the new <code>RGB</code> value for the definitions
     * identifier.
     */
    private void setDescendantRegistryValues(ColorDefinition definition,
            RGB newRGB) {
        ColorDefinition[] children = getDescendantColors(definition);

        for (int i = 0; i < children.length; i++) {
            if (isDefault(children[i])) {
                setDescendantRegistryValues(children[i], newRGB);
                setRegistryValue(children[i], newRGB);
                colorValuesToSet.put(children[i].getId(), newRGB);
            }
        }
    }

    /**
     * @param definition
     * @param datas
     */
    private void setDescendantRegistryValues(FontDefinition definition,
            FontData[] datas) {
        FontDefinition[] children = getDescendantFonts(definition);

        for (int i = 0; i < children.length; i++) {
            if (isDefault(children[i])) {
                setDescendantRegistryValues(children[i], datas);
                setRegistryValue(children[i], datas);
                fontValuesToSet.put(children[i].getId(), datas);
            }
        }
    }

    /**
     * @param definition
     * @param datas
     */
    protected void setFontPreferenceValue(FontDefinition definition,
            FontData[] datas) {
        setDescendantRegistryValues(definition, datas);
        fontPreferencesToSet.put(definition.getId(), datas);
    }

    /**
     * Updates the working registry.
     * @param definition
     * @param newRGB
     */
    protected void setRegistryValue(ColorDefinition definition, RGB newRGB) {
        colorRegistry.put(definition.getId(), newRGB);
    }

    /**
     * @param definition
     * @param datas
     */
    protected void setRegistryValue(FontDefinition definition, FontData[] datas) {
        fontRegistry.put(definition.getId(), datas);
    }

    /**
     * Swap in the color selection controls.
     */
    protected void swapColorControls() {
        controlAreaLayout.topControl = colorControls;
        controlArea.layout();
    }

    /**
     * Swap in the font selection controls.
     */
    protected void swapFontControls() {
        controlAreaLayout.topControl = fontControls;
        controlArea.layout();
    }

    /**
     * Swap in no controls (empty the control area)
     */
    protected void swapNoControls() {
        controlAreaLayout.topControl = null;
        controlArea.layout();
    }

    /**
     * Set the color list.
     * @param category the category to use.
     */
    private void updateCategorySelection(ThemeElementCategory category) {

        Composite previewControl = (Composite) previewMap.get(category);
        if (previewControl == null) {
            if (category != null) {
                try {
                    IThemePreview preview = getThemePreview(category);
                    if (preview != null) {
                        previewControl = new Composite(previewComposite,
                                SWT.NONE);
                        previewControl.setLayout(new FillLayout());
                        ITheme theme = getCascadingTheme();
                        preview.createControl(previewControl, theme);
                        previewSet.add(preview);
                    }
                } catch (CoreException e) {
                    previewControl = new Composite(previewComposite, SWT.NONE);
                    previewControl.setLayout(new FillLayout());
                    myApplyDialogFont(previewControl);
                    Text error = new Text(previewControl, SWT.WRAP
                            | SWT.READ_ONLY);
                    error.setText(RESOURCE_BUNDLE
                            .getString("errorCreatingPreview")); //$NON-NLS-1$
 WorkbenchPlugin
                            .log(
                                    RESOURCE_BUNDLE
                                            .getString("errorCreatePreviewLog"), StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e)); //$NON-NLS-1$
 }
            }
        }
        if (previewControl == null) {
            previewControl = getDefaultPreviewControl();
        }
        previewMap.put(category, previewControl);
        stackLayout.topControl = previewControl;
        previewComposite.layout();
    }

    /**
     * @param category the category
     * @return the preview for the category, or its ancestors preview if it does not have one.
     */
    private IThemePreview getThemePreview(ThemeElementCategory category)
            throws CoreException {
        IThemePreview preview = category.createPreview();
        if (preview != null) {
            return preview;
        }

        if (category.getParentId() != null) {
            int idx = Arrays.binarySearch(themeRegistry.getCategories(),
                    category.getParentId(), IThemeRegistry.ID_COMPARATOR);
            if (idx >= 0) {
                return getThemePreview(themeRegistry.getCategories()[idx]);
            }
        }

        return null;
    }

    /**
     * @return
     */
    private ITheme getCascadingTheme() {
        if (cascadingTheme == null) {
            cascadingTheme = new CascadingTheme(currentTheme, colorRegistry,
                    fontRegistry);
        }
        return cascadingTheme;
    }

    /**
     * Update the color controls based on the supplied definition.
     *
     * @param definition The currently selected <code>ColorDefinition</code>.
     */
    protected void updateColorControls(ColorDefinition definition) {
        if (definition == null) {
            colorResetButton.setEnabled(false);
            colorSelector.setEnabled(false);
            descriptionText.setText(""); //$NON-NLS-1$
 return;
        }

        colorSelector.setColorValue(getColorValue(definition));

        colorResetButton.setEnabled(!isDefault(definition));
        colorSelector.setEnabled(true);
        String description = definition.getDescription();
        descriptionText.setText(description == null ? "" : description); //$NON-NLS-1$
 }

    protected void updateFontControls(FontDefinition definition) {
        if (definition == null) {
            fontSystemButton.setEnabled(false);
            fontResetButton.setEnabled(false);
            fontChangeButton.setEnabled(false);
            descriptionText.setText(""); //$NON-NLS-1$
 return;
        }

        fontSystemButton.setEnabled(true);
        fontResetButton.setEnabled(!isDefault(definition));
        fontChangeButton.setEnabled(true);
        String description = definition.getDescription();
        descriptionText.setText(description == null ? "" : description); //$NON-NLS-1$
 }

    /**
     * Update for a change in the dialog font.
     * @param newFont
     */
    private void updateForDialogFontChange(Font newFont) {
        Iterator iterator = dialogFontWidgets.iterator();
        while (iterator.hasNext()) {
            ((Control) iterator.next()).setFont(newFont);
        }

        //recalculate the fonts for the tree
 labelProvider.clearFontCacheAndUpdate();
    }
    
    /**
     * Restore the selection state of the tree.
     *
     * @since 3.1
     */
    private void restoreTreeSelection() {
        String selectedElementString = getPreferenceStore().getString(
                SELECTED_ELEMENT_PREF);

        if (selectedElementString == null) {
            return;
        }

        Object element = findElementFromMarker(selectedElementString);
        if (element == null) {
            return;
        }

        tree.getViewer().setSelection(new StructuredSelection(element), true);
    }

    /**
     * Save the selection state of the tree.
     *
     * @since 3.1
     */
    private void saveTreeSelection() {
        IStructuredSelection selection = (IStructuredSelection) tree
                .getViewer().getSelection();
        Object element = selection.getFirstElement();
        StringBuffer buffer = new StringBuffer ();
        appendMarkerToBuffer(buffer, element);
        if (buffer.length() > 0) {
            buffer.append(((IThemeElementDefinition) element).getId());
        }
        getPreferenceStore().setValue(SELECTED_ELEMENT_PREF, buffer.toString());
    }

    /**
     * Restore the expansion state of the tree.
     *
     * @since 3.1
     */
    private void restoreTreeExpansion() {
        String expandedElementsString = getPreferenceStore().getString(
                EXPANDED_ELEMENTS_PREF);
        if (expandedElementsString == null) {
            return;
        }

        String [] expandedElementIDs = Util.getArrayFromList(expandedElementsString, EXPANDED_ELEMENTS_TOKEN);
        if (expandedElementIDs.length == 0) {
            return;
        }

        List elements = new ArrayList (expandedElementIDs.length);
        for (int i = 0; i < expandedElementIDs.length; i++) {
            IThemeElementDefinition def = findElementFromMarker(expandedElementIDs[i]);

            if (def != null) {
                elements.add(def);
            }
        }
        tree.getViewer().setExpandedElements(elements.toArray());
    }

    /**
     * Find the theme element from the given string. It will check the first
     * character against the known constants and then call the appropriate
     * method on the theme registry. If the element does not exist or the string
     * is invalid <code>null</code> is returned.
     *
     * @param string the string to parse
     * @return the element, or <code>null</code>
     */
    private IThemeElementDefinition findElementFromMarker(String string) {
        if (string.length() < 2) {
            return null;
        }

        char marker = string.charAt(0);
        String id = string.substring(1);
        IThemeElementDefinition def = null;
        switch (marker) {
        case MARKER_FONT:
            def = themeRegistry.findFont(id);
            break;
        case MARKER_COLOR:
            def = themeRegistry.findColor(id);
            break;
        case MARKER_CATEGORY:
            def = themeRegistry.findCategory(id);
            break;
        }
        return def;
    }

    /**
     * Saves the expansion state of the tree.
     *
     * @since 3.1
     */
    private void saveTreeExpansion() {
        Object [] elements = tree.getViewer().getExpandedElements();
        List elementIds = new ArrayList (elements.length);

        StringBuffer buffer = new StringBuffer ();
        for (int i = 0; i < elements.length; i++) {
            Object object = elements[i];
            appendMarkerToBuffer(buffer, object);

            if (buffer.length() != 0) {
                buffer.append(((IThemeElementDefinition) object).getId());
                elementIds.add(buffer.toString());
            }
            buffer.setLength(0);
        }

        for (Iterator i = elementIds.iterator(); i.hasNext();) {
            String id = (String ) i.next();
            buffer.append(id);
            if (i.hasNext()) {
                buffer.append(EXPANDED_ELEMENTS_TOKEN);
            }
        }

        getPreferenceStore()
                .setValue(EXPANDED_ELEMENTS_PREF, buffer.toString());
    }

    /**
     * @param buffer
     * @param object
     */
    private void appendMarkerToBuffer(StringBuffer buffer, Object object) {
        if (object instanceof FontDefinition) {
            buffer.append(MARKER_FONT);
        } else if (object instanceof ColorDefinition) {
            buffer.append(MARKER_COLOR);
        } else if (object instanceof ThemeElementCategory) {
            buffer.append(MARKER_CATEGORY);
        }
    }

    /**
     * Edit the currently selected font.
     *
     * @param display the display to open the dialog on
     * @since 3.2
     */
    private void editFont(Display display) {
        final FontDefinition definition = getSelectedFontDefinition();
        if (definition != null) {
            final FontDialog fontDialog = new FontDialog(fontChangeButton
                    .getShell());
            fontDialog.setFontList(getFontValue(definition));
            final FontData data = fontDialog.open();
            
            if (data != null) {
                setFontPreferenceValue(definition, fontDialog.getFontList());
                setRegistryValue(definition, fontDialog.getFontList());
            }

            updateFontControls(definition);
        }
    }
}

